using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using ValidationFramework.Configuration;
using ValidationFramework.Reflection;

namespace ValidationFramework.Web.ASP
{
    /// <summary>
    /// Adds <see cref="BaseValidator"/>s to <see cref="Control"/>s based on the <see cref="Rule"/>s on a <see cref="Type"/>.
    /// </summary>
    /// <remarks>
    /// The following <see cref="Rule"/>s are supported for front end javascript validation.
    /// <list type="bullet">
    ///  <item>
    ///		<see cref="CompareRule{T}"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="CompareRule{T}"/>. But only for the following types <see cref="short"/>, <see cref="int"/>, <see cref="long"/>, <see cref="decimal"/>, <see cref="double"/>, <see cref="DateTime"/><br/>
    ///   Note: see <B>Note on <see cref="DateTime"/>s</B> 
    ///	 </item>
    ///  <item>
    ///		<see cref="LengthStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RangeStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RangeRule{T}"/>. But only for the following types <see cref="short"/>, <see cref="int"/>, <see cref="long"/>, <see cref="decimal"/>, <see cref="double"/>, <see cref="DateTime"/><br/>
    ///   Note: see <B>Note on <see cref="DateTime"/>s</B> <br/>
    ///   Note: is either <see cref="RangeRule{T}.EqualsMaximumIsValid"/> or <see cref="RangeRule{T}.EqualsMinimumIsValid"/> are set to false no <see cref="BaseValidator"/>s will be generated. This is due to extensibility restrictions of the MS web validators and will be fixed in future versions.
    ///	 </item>
    ///  <item>
    ///		<see cref="RegexRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RequiredStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RequiredRule{T}"/>. Note: see <B>Note on <see cref="DateTime"/>s</B> 
    ///	 </item>
    /// </list>
    /// All <see cref="Rule"/>s, including those listed above, will be validated server side. <br/>
    /// 
    /// <B>Note on <see cref="DateTime"/>s</B>: <see cref="DateTime"/>s will only work if the <see cref="RequiredRule{T}.InitialValue"/>, <see cref="CompareRule{T}.ValueToCompare"/>, <see cref="RangeRule{T}.Minimum"/> and <see cref="RangeRule{T}.Maximum"/> is a <see cref="DateTime"/> with no time component. This is due to the limitation of both the <see cref="RequiredFieldValidator"/> and <see cref="CompareValidator"/>, since they only support "yyyy-MM-dd". Note that if a <see cref="RequiredRule{T}"/> is applied to a <see cref="DateTime"/> member that is <b>not</b> <see cref="Nullable{T}"/> <see cref="DateTime.MinValue"/> will be used as the <see cref="RequiredRule{T}.InitialValue"/>.
    /// </remarks>
    /// <seealso cref="AddAssociation(Control,string)"/>
    /// <seealso cref="AddAssociation(Control,string, Control)"/>
    /// <seealso cref="GenerateValidators"/>
    public abstract class BaseValidatorGenerator
    {
        #region Fields

        private Mode mode = Mode.ClientServer;

        #endregion


        #region Constructors

        static BaseValidatorGenerator()
        {
            Strategies = new StrategyCache<IClientValidationGenerator, RuleContext>();
            Strategies.AddStrategy(new CompareRuleClientValidationGenerator());
            Strategies.AddStrategy(new LengthStringRuleClientValidationGenerator());
            Strategies.AddStrategy(new RangeRuleClientValidationGenerator());
            Strategies.AddStrategy(new RequiredRuleClientValidationGenerator());
            Strategies.AddStrategy(new RequiredStringRuleClientValidationGenerator());
            Strategies.AddStrategy(new RegexRuleClientValidationGenerator());
            
        }

        /// <summary>
        /// Initialize a new instance of the <see cref="BaseValidatorGenerator"/> class.
        /// </summary>
        public BaseValidatorGenerator()
        {
            ValidationAssociations = new ValidationAssociationCollection();
        }

        #endregion


        #region Properties

        public static StrategyCache<IClientValidationGenerator, RuleContext> Strategies { get; private set; }

        /// <summary>
        /// Gets or sets a <see cref="ValidationAssociationCollection"/> containing all the <see cref="ValidationAssociation"/>s for this <see cref="ParameterValidatorGenerator"/>.
        /// </summary>
        public ValidationAssociationCollection ValidationAssociations
        {
            get;
            set;
        }


        /// <summary>
        /// Gets or sets a the type of validators that will be generated. Client Server or both.
        /// </summary>
        /// <remarks>If <see cref="Mode"/> is set to <see cref="ASP.Mode.Client"/> it is not necessary to attach to the server object required event. Not all <see cref="Rule"/>s can be validated on the client side so your business layer will be responsible for doing a validation of the object on the server side and displaying the message to the user.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is out of the accepted range.</exception>
        [Description("Type of validators that will be generated. Client Server or both.")]
        public Mode Mode
        {
            get
            {
                return mode;
            }
            set
            {
                if ((value < Mode.Client) || (value > Mode.ClientServer))
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                mode = value;
            }
        }


        /// <summary>
        /// Gets or sets the name of the validation group to which all the <see cref="BaseValidator.ValidationGroup"/>s will default to. 
        /// </summary>
        [Description("The name of the validation group to which all the BaseValidators will default to.")]
        public virtual string ValidationGroup
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the display behavior of the error message to which all the <see cref="BaseValidator.Display"/>s will default to. The default value is <see cref="ValidatorDisplay.Dynamic"/>. 
        /// </summary>
        [Description("The display behavior of the error message to which all the BaseValidators will default to.")]
        public ValidatorDisplay Display
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the context to pass to <see cref="Rule.Validate"/>
        /// </summary>
        public string Context
        {
            get;
            set;
        }

        #endregion


        #region Methods

        /// <summary>
        /// A shortcut method to add a new <see cref="ValidationAssociation"/> to <see cref="ValidationAssociations"/>.
        /// </summary>
        /// <param name="controlToValidate">The <see cref="Control"/> to validate.</param>
        /// <param name="memberName">The name of the member to validate.</param>
        /// <param name="containerControl">A <see cref="Control"/> to use to locate where the <see cref="BaseValidator"/> will be rendered. Null is accepted.</param>
        /// <returns>The <see cref="ValidationAssociation"/> that has been added to <see cref="ValidationAssociations"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="controlToValidate"/> is a null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="memberName"/> is a null reference.</exception>
        /// <exception cref="ArgumentException"><paramref name="memberName"/> is <see cref="string.Empty"/>.</exception>
        public ValidationAssociation AddAssociation(Control controlToValidate, string memberName, Control containerControl)
        {
            Guard.ArgumentNotNull(controlToValidate, "controlToValidate");
            Guard.ArgumentNotNullOrEmptyString(memberName, "memberName");
            var association = new ValidationAssociation(controlToValidate, memberName, containerControl);
            ValidationAssociations.Add(association);
            return association;
        }


        /// <summary>
        /// A shortcut method to add a new <see cref="ValidationAssociation"/> to <see cref="ValidationAssociations"/>.
        /// </summary>
        /// <param name="controlToValidate">The <see cref="Control"/> to validate.</param>
        /// <param name="memberName">The name of the member to validate.</param>
        /// <returns>The <see cref="ValidationAssociation"/> that has been added to <see cref="ValidationAssociations"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="controlToValidate"/> is a null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="memberName"/> is a null reference.</exception>
        /// <exception cref="ArgumentException"><paramref name="memberName"/> is <see cref="string.Empty"/>.</exception>
        public ValidationAssociation AddAssociation(Control controlToValidate, string memberName)
        {
            return AddAssociation(controlToValidate, memberName, null);
        }

        /// <summary>
        /// Get the <see cref="InfoDescriptor"/> for a specified member name.
        /// </summary>
        /// <param name="memberName">The member name to find get the <see cref="InfoDescriptor"/> for.</param>
        /// <returns>The <see cref="InfoDescriptor"/> for a specified member name.</returns>
        protected abstract InfoDescriptor GetInfoDescriptor(string memberName);


        /// <summary>
        /// Go through all the <see cref="ValidationAssociations"/> and add <see cref="BaseValidator"/>s to each.
        /// </summary>
        public IList<BaseValidator> GenerateValidators()
        {
            var baseValidators = new List<BaseValidator>();
            foreach (var association in ValidationAssociations)
            {
                var infoDescriptor = GetInfoDescriptor(association.MemberName);
                if (infoDescriptor != null)
                {
                    for (var ruleIndex = 0; ruleIndex < infoDescriptor.Rules.Count; ruleIndex++)
                    {
                        var rule = infoDescriptor.Rules[ruleIndex];
                        if (Mode != Mode.Client)
                        {
                            CreateServerValidator(association, baseValidators, rule, infoDescriptor);
                        }

                        if (Mode != Mode.Server)
                        {
                            CreateClientValidator(association, baseValidators, rule, infoDescriptor);
                        }
                    }
                }
                else
                {
                    throw new InvalidOperationException(string.Format("The member '{0}' could not be found or does not contain any validation rules.", association.MemberName));
                }
            }
            return baseValidators;
        }


        protected abstract void CreateServerValidator(ValidationAssociation association, IList<BaseValidator> validators, Rule rule, InfoDescriptor infoDescriptor);


        private void CreateClientValidator(ValidationAssociation association, List<BaseValidator> baseValidators, Rule rule, InfoDescriptor infoDescriptor)
        {
            var ruleContext = new RuleContext {Rule = rule, InfoDescriptor = infoDescriptor};

            var clientValidationGenerator = Strategies.FindStrategy(ruleContext);
            if (clientValidationGenerator != null)
            {
                var clientBaseValidators = clientValidationGenerator.CreateWebClientValidators(rule, infoDescriptor);
                for (var baseValidatorIndex = 0; baseValidatorIndex < clientBaseValidators.Count; baseValidatorIndex++)
                {
                    var clientBaseValidator = clientBaseValidators[baseValidatorIndex];

                    if (rule.UseErrorMessageProvider)
                    {
                        clientBaseValidator.ErrorMessage = ConfigurationService.ErrorMessageProvider.RetrieveErrorMessage(rule, null, null, null);
                    }
                    else
                    {
                        clientBaseValidator.ErrorMessage = rule.ErrorMessage;
                    }

                    baseValidators.Add(clientBaseValidator);
                    AddValidator(association, clientBaseValidator);
                }
            }
        }


        protected void AddValidator(ValidationAssociation association, BaseValidator baseValidator)
        {
            baseValidator.ControlToValidate = association.ControlToValidate.ID;
            baseValidator.ValidationGroup = ValidationGroup;
            baseValidator.Display = Display;
            if (association.ContainerControl == null)
            {
                var parent = association.ControlToValidate.Parent;
                var indexOf = parent.Controls.IndexOf(association.ControlToValidate);
                parent.Controls.AddAt(indexOf + 1, baseValidator);
            }
            else
            {
                association.ContainerControl.Controls.Add(baseValidator);
            }
        }

        #endregion
    }
}